/*******************************************************
 * UNIFOR     - Universidade de Fortaleza              *
 * MIA        - Mestrado em Informatica Aplicada       *
 * Disciplina - Modelagem Numerica                     *
 * Aluno      - Hermano Albuquerque Lira               *
 * Professor  - Furlan                                 *
 *                                                     *
 *                 Trabalho Final                      *
 *                                                     *
 *******************************************************/
package br.unifor.modelagem.numerica.trabalhofinal;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import br.unifor.modelagem.numerica.ClasseDistribuicao;

public final class Estatistica {

	private Map<TipoEstatistica, Integer> mapaTipoIndividuo = new HashMap<TipoEstatistica, Integer>();

	private static Map<Integer, Estatistica> mapaEstatistica = new HashMap<Integer, Estatistica>();

	private Estatistica() {
		// Inicializa mapaTipoIndividuo
		mapaTipoIndividuo.put(TipoEstatistica.IMUNE, 0);
		mapaTipoIndividuo.put(TipoEstatistica.PSEUDO_IMUNE, 0);
		mapaTipoIndividuo.put(TipoEstatistica.NOVO_INFECTADO, 0);
		mapaTipoIndividuo.put(TipoEstatistica.INFECTADO, 0);
		mapaTipoIndividuo.put(TipoEstatistica.SADIO, 0);
		mapaTipoIndividuo.put(TipoEstatistica.NASCIMENTO, 0);
	}
	
	/**
	 * Instancia da Estatistica associada a uma Atualizacao.
	 */
	public static Estatistica getEstatisticaAtual() {
		if (mapaEstatistica.get(Atualizacao.get()) == null) {
			mapaEstatistica.put(Atualizacao.get(), new Estatistica());
		}
		return mapaEstatistica.get(Atualizacao.get());
	}

	/**
	 * Calcula Media.
	 */
	private static double media(TipoEstatistica tipo) {
	
		int somatorio = 0;
	
		for (Integer key : mapaEstatistica.keySet()) {
			Estatistica estatistica = mapaEstatistica.get(key);
			somatorio += estatistica.totalTipoIndividuo(tipo);
		}
	
		int totalAtualizacoes = mapaEstatistica.keySet().size();
	
		return somatorio / totalAtualizacoes;
	}

	private static double desvioPadrao(TipoEstatistica tipo) {
	
		double somatorioQuadrado = 0;
	
		for (Integer key : mapaEstatistica.keySet()) {
			Estatistica estatistica = mapaEstatistica.get(key);
			int i = estatistica.totalTipoIndividuo(tipo);
			somatorioQuadrado += Math.pow(i - media(TipoEstatistica.SADIO), 2);
		}
	
		return Math.sqrt(somatorioQuadrado / mapaEstatistica.keySet().size());
	}

	private static double mediana(TipoEstatistica tipo) {
		
		int[] listaValores = new int[mapaEstatistica.keySet().size()+1];
	
		for (Integer key : mapaEstatistica.keySet()) {
			Estatistica estatistica = mapaEstatistica.get(key);
			listaValores[key] = estatistica.totalTipoIndividuo(tipo);
		}

		Arrays.sort(listaValores);
		
		int mediana;
		
		// Se lista de valores é um conjunto par de valores
		if (listaValores.length % 2 == 1) {
			mediana = (listaValores[listaValores.length/2] + listaValores[(listaValores.length/2)+1])/2;
		} else {
			mediana = listaValores[listaValores.length/2];
		}

		return mediana;
	}

	private static int[] totaisPorTipo(TipoEstatistica tipo) {
	
		int[] valores = new int[mapaEstatistica.keySet().size()];
	
		for (Integer key : mapaEstatistica.keySet()) {
			Estatistica estatistica = mapaEstatistica.get(key);
			valores[key-1] = estatistica.totalTipoIndividuo(tipo);
		}
	
		return valores;
	}

	public static void imprimirMedias() {
		System.out.println("------------------------------------------------------------------------");
		System.out.println("Media de Imunes: " + media(TipoEstatistica.IMUNE));
		System.out.println("Media de Pseudo-imunes: " + media(TipoEstatistica.PSEUDO_IMUNE));
		System.out.println("Media de Infectantes gerados: " + media(TipoEstatistica.NOVO_INFECTADO));
		System.out.println("Media de Infectados: " + media(TipoEstatistica.INFECTADO));
		System.out.println("Media de Sadios: " + media(TipoEstatistica.SADIO));
		System.out.println("Media de Nascimentos: " + media(TipoEstatistica.NASCIMENTO));
		System.out.println("------------------------------------------------------------------------");
	}

	public static void imprimirDeviosPadroes() {
		System.out.println("------------------------------------------------------------------------");
		System.out.println("Devio Padrao de Imunes: " + desvioPadrao(TipoEstatistica.IMUNE));
		System.out.println("Devio Padrao de Pseudo-imunes: " + desvioPadrao(TipoEstatistica.PSEUDO_IMUNE));
		System.out.println("Devio Padrao de Infectantes gerados: " + desvioPadrao(TipoEstatistica.NOVO_INFECTADO));
		System.out.println("Devio Padrao de Infectados: " + desvioPadrao(TipoEstatistica.INFECTADO));
		System.out.println("Devio Padrao de Sadios: " + desvioPadrao(TipoEstatistica.SADIO));
		System.out.println("Devio Padrao de Nascimentos: " + desvioPadrao(TipoEstatistica.NASCIMENTO));
		System.out.println("------------------------------------------------------------------------");
	}

	public static void imprimirMedianas() {
		System.out.println("------------------------------------------------------------------------");
		System.out.println("Mediana de Imunes: " + mediana(TipoEstatistica.IMUNE));
		System.out.println("Mediana de Pseudo-imunes: " + mediana(TipoEstatistica.PSEUDO_IMUNE));
		System.out.println("Mediana de Infectantes gerados: " + mediana(TipoEstatistica.NOVO_INFECTADO));
		System.out.println("Mediana de Infectados: " + mediana(TipoEstatistica.INFECTADO));
		System.out.println("Mediana de Sadios: " + mediana(TipoEstatistica.SADIO));
		System.out.println("Mediana de Nascimentos: " + mediana(TipoEstatistica.NASCIMENTO));
		System.out.println("------------------------------------------------------------------------");
	}
	
	public static ClasseDistribuicao[] distribuicao(TipoEstatistica tipo) {
		
		int[] valores = Estatistica.totaisPorTipo(tipo);
		
		Arrays.sort(valores);
		
		// Intervalo
		int menor = valores[0];
		int maior = valores[valores.length-1];
		
		// Número de classes
		double numeroClasses = Math.floor(Math.sqrt(valores.length)+0.5d);
		
		if (numeroClasses < 5) numeroClasses = 5;
		if (numeroClasses > 15) numeroClasses = 15;
		
		// Amplitude das classes
		int amplitude = (int) Math.floor(((maior - menor)/numeroClasses)+0.5d);
		
		ClasseDistribuicao[] distrFrequencia = new ClasseDistribuicao[(int) numeroClasses];
		
		int valorInicial = menor;
		int valorFinal = valorInicial+amplitude;
		int ponteiro = 0;
		
		for (int i = 0 ; i < numeroClasses ; i++) {
			distrFrequencia[i] = new ClasseDistribuicao(valorInicial, valorFinal);
			
			int frequencia = 0;

			// Contar valores
			while (ponteiro < valores.length && (valores[ponteiro] >= valorInicial && valores[ponteiro] <= valorFinal)) {
				frequencia++;
				ponteiro++;
			}
			
			distrFrequencia[i].setFrequencia(frequencia);
			
			valorInicial = valorFinal+1;
			valorFinal = valorInicial+amplitude;
		}

		return distrFrequencia;
	}

	private void incrementTipo(TipoEstatistica tipo) {
		Integer contador = mapaTipoIndividuo.get(tipo);
		contador = contador + 1;
		mapaTipoIndividuo.put(tipo, contador);
	}

	private Integer totalTipoIndividuo(TipoEstatistica tipo) {
		return mapaTipoIndividuo.get(tipo) == null ? 0 : mapaTipoIndividuo.get(tipo);
	}

	public void imprimirTotaisAtual() {
		System.out.println("Numero de Imunes: " + mapaTipoIndividuo.get(TipoEstatistica.IMUNE));
		System.out.println("Numero de Pseudo-imunes: " + mapaTipoIndividuo.get(TipoEstatistica.PSEUDO_IMUNE));
		System.out.println("Numero de Infectantes gerados: " + mapaTipoIndividuo.get(TipoEstatistica.NOVO_INFECTADO));
		System.out.println("Numero de Infectados: " + mapaTipoIndividuo.get(TipoEstatistica.INFECTADO));
		System.out.println("Numero de Sadios: " + mapaTipoIndividuo.get(TipoEstatistica.SADIO));
		System.out.println("Numero de Nascimentos: " + mapaTipoIndividuo.get(TipoEstatistica.NASCIMENTO));
	}

	public void incrementNascimento() {
		Integer contador = mapaTipoIndividuo.get(TipoEstatistica.NASCIMENTO);
		contador = contador + 1;
		mapaTipoIndividuo.put(TipoEstatistica.NASCIMENTO, contador);
	}

	public void incrementNovoInfectado() {
		Integer contador = mapaTipoIndividuo.get(TipoEstatistica.NOVO_INFECTADO);
		contador = contador + 1;
		mapaTipoIndividuo.put(TipoEstatistica.NOVO_INFECTADO, contador);
	}

	public void incrementTipoIndividuo(Individuo individuo) {
	
		if (individuo.getTipo().equals(TipoIndividuo.SADIO)) {
			incrementTipo(TipoEstatistica.SADIO);
		} else
	
		if (individuo.getTipo().equals(TipoIndividuo.IMUNE)) {
			incrementTipo(TipoEstatistica.IMUNE);
		} else
	
		if (individuo.getTipo().equals(TipoIndividuo.PSEUDO_IMUNE)) {
			incrementTipo(TipoEstatistica.PSEUDO_IMUNE);
		} else
	
		if (individuo.getTipo().equals(TipoIndividuo.INFECTADO)) {
			incrementTipo(TipoEstatistica.INFECTADO);
		}
	}
}
